import collections
import decimal
import types
from datetime import datetime, timedelta
from random import randint

from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.conf import settings
from django.db import transaction
from django.db.models import Count
from django.urls import reverse
from django_filters.rest_framework import DjangoFilterBackend
from django_redis import get_redis_connection
from rest_framework.permissions import IsAuthenticated
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import GenericViewSet

from saas_main.filters.issue_filter import IssueFilter
from saas_main.pagination.issue_pagination import IssuePagination
from saas_main.permission.project_permission import IsJoinPerssion, FilePerssion
from saas_main.serializer.project_serializer import *
from utils.dict_compare import obj_to_dict, compare_data_dict
from utils.encrypt import uid
from utils.tencent_cos import upload_file, delete_file, delete_files, get_temp_credential
from saas_main.tasks import *


class ProjectDashboard(APIView):
    permission_classes = [IsAuthenticated, IsJoinPerssion]

    def get(self, request, *args, **kwargs):
        today = datetime.now().date()
        date_dict = collections.OrderedDict()
        for i in range(0, 30):
            date = today - timedelta(days=i)
            date_dict[date.strftime("%Y-%m-%d")] = [datetime.strptime(str(date), "%Y-%m-%d").timestamp() * 1000, 0]

        qs = models.Issues.objects.filter(project=request.tracer.project,
                                          create_datetime__range=(
                                              today - timedelta(days=30), today + timedelta(days=1)))
        if 'mysql' in settings.DATABASES.get('default').get('ENGINE'):
            db_query = "DATE_FORMAT(saas_main_issues.create_datetime,'%%Y-%%m-%%d')"
        else:
            db_query = "strftime('%%Y-%%m-%%d',saas_main_issues.create_datetime)"
        group_by_day_qs = qs.extra(select={"ctime": db_query}).values(
            'ctime').annotate(count=Count("id"))

        # <QuerySet [{'ctime': '2023-11-02', 'count': 3}]>
        # mysql的字符串格式化函数时DATA_FORMAT(saas_main_issues.create_datetime,'%%Y-%%m-%%d')

        for item in group_by_day_qs:
            date_dict[item['ctime']][1] = item['count']

        return Response({"ret": 0, "data": list(date_dict.values())})


class ProjectWikiViewSet(GenericViewSet):
    queryset = models.Wiki.objects
    serializer_class = WikiSerializer
    permission_classes = [IsAuthenticated, IsJoinPerssion]

    def create(self, request, *args, **kwargs):
        data = request.data.dict()
        if request.data.get('parent'):
            new_depth = models.Wiki.objects.filter(id=data.get('parent')).first().depth + 1
            data.update({'depth': new_depth})
        serializer = self.get_serializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return Response({"ret": 0, 'msg': "ok", "data": serializer.data})
        return Response({"ret": 1, "errors": serializer.errors})

    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset().filter(project=request.query_params.get('project')).order_by('depth')
        serializer = self.get_serializer(queryset, many=True)
        return Response({"ret": 0, 'msg': "ok", "data": serializer.data})

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response({"ret": 0, 'msg': "ok", "data": serializer.data})

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        instance.delete()
        return Response({"ret": 0, 'msg': "ok"})

    def update(self, request, *args, **kwargs):
        data = request.data.dict()
        if request.data.get('parent'):
            new_depth = models.Wiki.objects.filter(id=data.get('parent')).first().depth + 1
        else:
            new_depth = 0

        data.update({'depth': new_depth})
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=data)

        if serializer.is_valid():
            serializer.save()
            return Response({"ret": 0, 'msg': "ok", "data": serializer.data})
        else:
            return Response({"ret": 1, "errors": serializer.errors})


class ProjectWikiUpload(APIView):
    # parser_classes = [MultiPartParser]
    renderer_classes = [JSONRenderer, ]
    permission_classes = [IsAuthenticated, IsJoinPerssion]

    def post(self, request, project):
        print(request)
        file_obj = request.data.get('editormd-image-file')
        name = file_obj.name
        img_type = name.split('.')[-1].lower()
        new_name = str(request.user.mobile) + datetime.now().strftime("_%Y%m%d%H%M%S_") + str(
            randint(0, 99999)) + "." + img_type
        url = upload_file(
            request.tracer.project.bucket,
            region=request.tracer.project.region,
            img_obj=file_obj,
            obj_name=new_name
        )

        result = {
            'success': 1,
            'message': "jdshjshd",
            'url': url
        }
        return Response(result)


class ProjectFileViewSet(GenericViewSet):
    queryset = models.FileRepository.objects
    serializer_class = FileSerializer
    permission_classes = [IsAuthenticated, IsJoinPerssion]

    def create(self, request, *args, **kwargs):
        data = request.data
        serializer = self.get_serializer(data=data)
        if serializer.is_valid():
            if data.get('file_type') == '1':
                try:
                    with transaction.atomic():
                        f_obj = serializer.save()
                        project = models.Project.objects.select_for_update().filter(id=data.get('project')).first()
                        project.used_space += decimal.Decimal(round(f_obj.file_size / (1024 * 1024), 2))
                        project.save()
                except Exception as e:
                    return Response({'ret': 1, "errors": {"non_field_errors": [f"上传失败，请稍后重试！{str(e)}"]}})
            else:
                serializer.save()
            return Response({"ret": 0, 'msg': "ok", "data": serializer.data})
        return Response({"ret": 1, "errors": serializer.errors})

    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset().filter(
            parent=request.query_params.get('folder_id'),
            project=request.query_params.get('project')).order_by('-file_type')
        serializer = self.get_serializer(queryset, many=True)
        return Response({"ret": 0, 'msg': "ok", "data": serializer.data})

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        if instance.file_type == 1:  # 删除文件
            try:
                with transaction.atomic():
                    project = models.Project.objects.select_for_update().filter(id=instance.project.id).first()
                    project.used_space -= decimal.Decimal(round(instance.file_size / (1024 * 1024), 2))
                    project.save()  # 将项目的容量进行归还
                    # 删除cos中的文件记录
                    delete_file(instance.project.bucket, instance.project.region, instance.key)
                    instance.delete()
            except Exception as e:
                return Response({'ret': 1, "errors": {"non_field_errors": [f"删除失败，请稍后重试！{str(e)}"]}})
        else:  # 删除文件夹
            try:
                with transaction.atomic():
                    total_size = 0  # 用于记录删除文件的大小
                    objects = {
                        "Quiet": "true",
                        "Object": []
                    }
                    folder_list = [instance]
                    for folder_obj in folder_list:
                        qs = models.FileRepository.objects.filter(parent=folder_obj)
                        for obj in qs:
                            if obj.file_type == 1:
                                total_size += obj.file_size  # 将删除文件大小累加
                                objects['Object'].append({"Key": obj.key})  # 将要删除的文件名字放入删除列表中
                            else:
                                folder_list.append(obj)
                    project = models.Project.objects.select_for_update().filter(id=instance.project.id).first()
                    project.used_space -= decimal.Decimal(round(total_size / (1024 * 1024), 2))
                    project.save()  # 将项目的容量进行归还
                    if objects['Object']:
                        delete_files(instance.project.bucket, instance.project.region, objects)
                    instance.delete()
            except Exception as e:
                return Response({'ret': 1, "errors": {"non_field_errors": [f"删除失败，请稍后重试！{str(e)}"]}})
        return Response({"ret": 0, 'msg': "ok"})

    def update(self, request, *args, **kwargs):
        data = request.data
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=data, partial=True)

        if serializer.is_valid():
            serializer.save()
            return Response({"ret": 0, 'msg': "ok", "data": serializer.data})
        else:
            return Response({"ret": 1, "errors": serializer.errors})


class ProjectFileCredential(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, project):
        res = get_temp_credential(request.tracer.project.bucket, request.tracer.project.region)
        return Response({"ret": 0, "data": res})


class ProjectFileSizeCheck(APIView):
    permission_classes = [IsAuthenticated, FilePerssion]

    def post(self, request, project):
        return Response({"ret": 0, "msg": 'ok'})


class ProjectIssueViewSet(GenericViewSet):
    queryset = models.Issues.objects
    serializer_class = IssueSerializer
    permission_classes = [IsAuthenticated, IsJoinPerssion]
    pagination_class = IssuePagination
    filter_backends = [DjangoFilterBackend]
    filterset_class = IssueFilter

    def create(self, request, *args, **kwargs):
        data = request.data
        serializer = self.get_serializer(data=data)
        if serializer.is_valid():
            try:
                with transaction.atomic():
                    serializer.save()
                    return Response({"ret": 0, 'msg': "ok", "data": serializer.data})
            except Exception as e:
                return Response({"ret": 1, "errors": str(e)})
        return Response({"ret": 1, "errors": serializer.errors})

    def list(self, request, *args, **kwargs):
        queryset = self.filter_queryset(
            self.get_queryset().filter(project=request.query_params.get('project')).order_by('-id', 'status'))
        page = self.paginate_queryset(queryset)
        serializer = self.get_serializer(instance=page, many=True)
        return Response({"ret": 0, 'msg': "ok", "data": serializer.data,
                         "page": {
                             'page_count': self.paginator.page.paginator.num_pages,
                             "next": self.paginator.get_next_link(),
                             'previous': self.paginator.get_previous_link(),
                             "current_page": self.paginator.page.number,
                             'page_location': str(self.paginator.page)
                         }})

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        instance.delete()
        return Response({"ret": 0, 'msg': "ok"})

    def update(self, request, *args, **kwargs):
        data = request.data
        data.pop('project')
        instance = self.get_object()
        instance_dict = obj_to_dict(instance)
        serializer = self.get_serializer(instance, data=data, partial=True)
        if serializer.is_valid():
            try:
                with transaction.atomic():
                    data_orderdict = serializer.validated_data
                    changes = compare_data_dict(dict(data_orderdict), instance_dict, instance)
                    serializer.save()
                    reply_data = {
                        'reply_type': 1,
                        'issues': instance,
                        'content': ''.join(changes),
                        "creator": request.user,
                    }
                    models.IssuesReply.objects.create(**reply_data)
            except Exception as e:
                return Response({"ret": 1, "errors": str(e)})
            return Response({"ret": 0, 'msg': "ok", "data": serializer.data})
        else:
            return Response({"ret": 1, "errors": serializer.errors})


# TODO:回复的新增和查看，问题展示层技术并添加跳转链接

class ProjectIssueReplyViewSet(GenericViewSet):
    queryset = models.IssuesReply.objects
    serializer_class = IssueReplySerializer
    permission_classes = [IsAuthenticated, IsJoinPerssion]

    def create(self, request, *args, **kwargs):
        data = request.data
        serializer = self.get_serializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return Response({"ret": 0, 'msg': "ok", "data": serializer.data})
        return Response({"ret": 1, "errors": serializer.errors})

    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset().filter(issues=request.query_params.get('issues'))
        serializer = self.get_serializer(queryset, many=True)
        return Response({"ret": 0, 'msg': "ok", "data": serializer.data})


class ProjectInviteViewSet(GenericViewSet):
    queryset = models.ProjectInvite.objects
    serializer_class = InviteSerializer
    permission_classes = [IsAuthenticated, IsJoinPerssion]

    def create(self, request, *args, **kwargs):
        # 判断是否有权生成邀请码
        project_obj = models.Project.objects.get(id=request.data.get("project"))
        if request.user != project_obj.creator:
            return Response({"ret": 1, "errors": {"count": ["你不是此项目的创建者，无权邀请用户。"]}})

        # 判断是否有未过期的可用邀请码，如有则直接返回
        if code_obj := self.get_queryset().filter(creator=request.user, project=project_obj).first():
            if (code_obj.create_datetime + timedelta(minutes=code_obj.period)) > datetime.now():
                if not code_obj.count or (code_obj.use_count < code_obj.count):
                    link = f"{request.scheme}://{request.get_host()}{reverse('main:invite_code')}" + \
                           f"?code={code_obj.code}&project={request.data.get('project')}"
                    return Response({"ret": 0, 'msg': "已经生成了邀请码", 'link': link})
                else:
                    code_obj.delete()
            else:
                code_obj.delete()

        data = request.data.dict()
        if not data.get('count'):
            data.pop("count")
        data["code"] = uid(request.user.mobile)
        data['creator'] = request.user.id
        serializer = self.get_serializer(data=data)
        if serializer.is_valid():
            serializer.save()
            link = f"{request.scheme}://{request.get_host()}{reverse('main:invite_code')}" + \
                   f"?code={serializer.data.get('code')}&project={data.get('project')}"
            return Response({"ret": 0, 'msg': "ok", "data": serializer.data, 'link': link})
        return Response({"ret": 1, "errors": serializer.errors})


class ProjectStatistics(APIView):
    permission_classes = [IsAuthenticated, IsJoinPerssion]

    def get(self, request, *args, **kwargs):
        data = request.query_params
        time_range = (start := datetime.now().date() - timedelta(days=6), start + timedelta(days=7))
        if data.get('start') and data.get('end'):
            time_range = (data.get('start'), data.get('end'))

        pie_chart = self._pie_data(data, time_range)
        column_chart = self._column_chart(data, time_range)

        return Response({"ret": 0, "data": {"pie_chart": pie_chart, "column_chart": column_chart}})

    def _pie_data(self, data, time_range):
        qs = models.Issues.objects.filter(project_id=data.get("project"),
                                          create_datetime__range=time_range).values(
            'priority').annotate(count=Count('id'))
        map_dict = {k: v for k, v in models.Issues.priority_choices}
        pie_chart = []
        for item in qs:
            pie_chart.append({"name": map_dict.get(item['priority']), "y": item['count']})
        return pie_chart

    def _column_chart(self, data, time_range):
        issue_qs = models.Issues.objects.filter(project=data.get('project'),
                                                create_datetime__range=time_range).select_related('assign')

        temp_dict = {}
        for i in issue_qs:
            if i.assign:
                name = i.assign.realname
            else:
                name = '未指定'
            if temp_dict.get(name):
                continue
            count_info = issue_qs.filter(assign=i.assign).values('status').annotate(count=Count('id'))

            num_list = [0, 0, 0, 0, 0, 0, 0]
            for num in count_info:
                num_list[num['status'] - 1] = num["count"]
                temp_dict.update({name: num_list})

        data_list = []
        no_name = temp_dict.pop("未指定", None)
        map_dict = {k: v for k, v in models.Issues.status_choices}
        for i in map_dict.keys():
            ll = []
            for n in temp_dict.values():
                ll.append(n[i - 1])
            if no_name:
                ll.append(no_name[i - 1])
            data_list.append({"name": map_dict[i], "data": ll})
        categories = list(temp_dict.keys())
        if no_name:
            categories.append("未指定")

        return {"categories": categories, "series": data_list}


class ProjectCases(APIView):
    permission_classes = [IsAuthenticated, IsJoinPerssion]

    def post(self, request, *args, **kwargs):
        data = request.data
        jenkins = JenkinsSdk()
        res = jenkins.build_job(data.get("case_name"), **data.get("params") if data.get("params") else {})
        if res == "ok":
            return Response({"ret": 0, "msg": res})
        else:
            return Response({"ret": 1, 'msg': res})

    def get(self, request, *args, **kwargs):
        data = request.query_params
        jenkins = JenkinsSdk()
        if data.get("refresh"):
            res = jenkins.get_job_info(data.get("case_name"))
            return Response({"ret": 0, "msg": res})
        res = jenkins.get_console(data.get("case_name"))
        if isinstance(res, types.GeneratorType):
            return Response({"ret": 1})
        return Response({"ret": 0, "msg": res})

    def put(self, request, *args, **kwargs):
        group = request.data.get("case_name")
        conn = get_redis_connection('default')
        if conn.get(f'channels_{group}_key'):
            return Response({"ret": 0})
        else:
            conn.set(f'channels_{group}_key', 1, ex=1200)
            self.send_console_msg(group)
            conn.delete(f'channels_{group}_key')
            return Response({"ret": 1})

    def send_console_msg(self, group):
        channel_layer = get_channel_layer()
        jenkins = JenkinsSdk()
        res = jenkins.get_console(group)
        if isinstance(res, types.GeneratorType):
            for i in res:
                i = i.encode("ISO-8859-1").decode("utf-8")
                async_to_sync(channel_layer.group_send)(group, {'type': "xx.oo", 'message': f"{i}"})
        else:
            async_to_sync(channel_layer.group_send)(group, {'type': "xx.oo", 'message': res})
        get_jenkins_list.delay()
        async_to_sync(channel_layer.group_send)(group, {'type': "xx.oo", 'message': "close"})

